#include "myD3D.h"
#include "utilities.h"

// constructor & destructor
myD3D::myD3D(){}
myD3D::~myD3D(){
	if (frame){
		CMeshHierarchy mh;
		D3DXFrameDestroy(frame, &mh);
	}
	if (anim)
		anim->Release();

	// free the list of primitives
	if  (!prims.empty()){
		list<lpmyD3DBuffer>::iterator i;
		for (i = prims.begin(); i != prims.end(); i++){
			(**i).vb->Release();
			delete (*i);
		}
	}
	// free all meshes
	if (!meshes.empty()){
		list<lpmyD3DMesh>::iterator i;
		for (i = meshes.begin(); i != meshes.end(); i++){
			(**i).mesh->Release();
			delete (**i).mats;
			delete (**i).texs;
			delete (*i);
		}
	}
	// release all textures
	if (!textures.empty()){
		list<lpmyD3DTexture>::iterator i;
		for (i = textures.begin();  i != textures.end(); i++){
			(**i).tex->Release();
			delete (*i);
		}
	}
	// release all directX objects
	d3ddev->Release();
	d3d->Release();
}

// a MUST finction to initialize directX properly and assign it to a window
//	_hWnd - handle to a window
//	fullscreen - do we want fullscreen?
//	w,h - width and height of the window
bool myD3D::Init(HWND _hWnd, bool fullscreen, int w, int h){
	// if directX is not alreade initialzed ...
	if (NULL == d3d) return false;

	// create directX instance - ver 9
	d3d = Direct3DCreate9(D3D_SDK_VERSION);
	if (!d3d) return false;

	// clear out structure and fill its necessary fields
	ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS));
	d3dpp.Windowed = !fullscreen;				// do we want fullscreen
	d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;	// set back buffer and forget old frames
	d3dpp.hDeviceWindow = _hWnd;				// set the window handle
	d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8;	// set the back buffer format to xRGB (32-bit)
	d3dpp.BackBufferHeight = h;					// set the height of the back buffer
	d3dpp.BackBufferWidth = w;					// set the width of the back buffer
	d3dpp.AutoDepthStencilFormat = D3DFMT_D16;	// 16-bit z-buffer type
	d3dpp.EnableAutoDepthStencil = true;		// let the directX take care of z-buffer

	// try to init directX Device in defalut adapter and as much hardwere involved as possible
	if (S_OK != d3d->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, _hWnd, 
								  D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &d3ddev)) return false;

	// setup camera 
	setLookAt();
	// setup viewing perspective
	setPerspective();

	// set up the point light in camera
	D3DLIGHT9 light;
	ZeroMemory(&light, sizeof(D3DLIGHT9));
	light.Type = D3DLIGHT_DIRECTIONAL; //D3DLIGHT_POINT;
	light.Ambient.a = light.Diffuse.a = 1.0f;
	light.Ambient.r = 0.0f; light.Diffuse.r = 0.6f;
	light.Ambient.g = 0.0f; light.Diffuse.g = 0.6f;
	light.Ambient.b = 0.0f; light.Diffuse.b = 0.6f;
	light.Direction = D3DXVECTOR3(0.0f, 0.0f, -1.0f);

	setLight(0, true, &light);

	// enable z-buffer and add some ambient light
	d3ddev->SetRenderState(D3DRS_ZENABLE, true);
	d3ddev->SetRenderState(D3DRS_AMBIENT, D3DCOLOR_XRGB(20, 20, 20));

	// normalize normals ( ||n||=1 )
	d3ddev->SetRenderState(D3DRS_NORMALIZENORMALS, TRUE);

	// Turn off culling
    d3ddev->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);

	// enable blending
	d3ddev->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
	d3ddev->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
	d3ddev->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
	d3ddev->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
	
	// if successfully initialized remember the window handle
	hWnd = _hWnd;
	return true;
}

// set LookAt
bool myD3D::setLookAt(){
	D3DXMATRIX look;
	D3DXMatrixLookAtLH(&look, &D3DXVECTOR3(-30.0f, 15.0f, 30.0f),	// camera position
							  &D3DXVECTOR3(0.0f, 0.0f, 0.0f),	// the lookAt position
							  &D3DXVECTOR3(0.0f, 1.0f, 0.0f));	// the up direction		
	return setLookAt(&look);
}
// 2nd version of setLookAt() using 3 3DVectors (camera, lookAtPoint, upPos)
bool myD3D::setLookAt(D3DXVECTOR3 *camPosition, D3DXVECTOR3 *lookAtPosition, D3DXVECTOR3 *upDirection){
	D3DXMATRIX look;
	D3DXMatrixLookAtLH(&look, camPosition, lookAtPosition, upDirection);
	return setLookAt(&look);
}
// 3rd version of setLookAt() using 4x4 view transform matrix
bool myD3D::setLookAt(D3DXMATRIX *view){
	if (S_OK != d3ddev->SetTransform(D3DTS_VIEW, view)) return false;
	return true;
}

// simplest version of setPerspective() - used in ::Init()
bool myD3D::setPerspective(){
	D3DXMATRIX pers;
	D3DXMatrixPerspectiveFovLH(&pers, D3DXToRadian(45.0f), 
		(float)d3dpp.BackBufferWidth/(float)d3dpp.BackBufferHeight, 0.1f, 1000.0f);
	return setPerspective(&pers);
}
// 2d version of setPerspective() - changes perspective according to user input
//	fovRadian - angle Field Of View (in radians)
//	aspect_WOverH - aspect=width/height
//	z_near, z_far - nearest and furthest clipping plane
bool myD3D::setPerspective(float fovRadian, float aspect_WOverH, float z_near, float z_far){
	D3DXMATRIX pers;
	D3DXMatrixPerspectiveFovLH(&pers, fovRadian, aspect_WOverH, z_near, z_far);
	return setPerspective(&pers);
}
// 3rd version of setPerspective() where projection matrix is passed as a parameter
bool myD3D::setPerspective(D3DXMATRIX *projectionMatrix){
	if(S_OK != d3ddev->SetTransform(D3DTS_PROJECTION, projectionMatrix)) return false;
	return true;
}

//setup light in the scene
void myD3D::setLight(DWORD index, bool makeOn, D3DLIGHT9 *light){
	if (makeOn){		// turning the light on
		d3ddev->SetLight(index, light);
		d3ddev->LightEnable(index, makeOn);
	}else{				// turning the light off
		d3ddev->LightEnable(index, makeOn);
	}
}

// set on/off 3D lighting
void myD3D::light3d(bool on){
	setRendState(D3DRS_LIGHTING, on);
}

// change renderer state to coustom one
bool myD3D::setRendState(D3DRENDERSTATETYPE state, DWORD value){
	if (S_OK != d3ddev->SetRenderState(D3DRS_LIGHTING, value)) return false;
	return true;
}

// load texture form the image file specified as 'path'
// texture is added to testures list and pointer to it returned
//	'colorMaskKey' specifies the masking color, elements with it will be 
//		transparent
LPDIRECT3DTEXTURE9 myD3D::addTextureFromFile(LPCWSTR path, D3DCOLOR *colorMaskKey){
	lpmyD3DTexture buf = (lpmyD3DTexture) malloc(sizeof(myD3DTexture));
	if (NULL == colorMaskKey){
		if (S_OK != D3DXCreateTextureFromFile(d3ddev, path, &(buf->tex))){
			delete (lpmyD3DTexture) buf;
			return NULL;
		}
	}else{
		if (S_OK != D3DXCreateTextureFromFileEx(d3ddev, path, D3DX_DEFAULT, D3DX_DEFAULT, 
			D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, 
			*colorMaskKey, NULL, NULL, &(buf->tex))){
				delete (lpmyD3DTexture) buf;
				return NULL;
		}
	}
	textures.push_back(buf);
	return buf->tex;
}

// add another primitive to renderer primitive list
bool myD3D::addPrimitive(void *data, DWORD _fvf, UINT _fvfstructsize, D3DPRIMITIVETYPE _type,
			UINT count, D3DXMATRIX *_transform, D3DMATERIAL9 *_mat, LPDIRECT3DTEXTURE9 tex1){
	// allocate memory for new primitive
	lpmyD3DBuffer buf = (lpmyD3DBuffer) malloc(sizeof(myD3DBuffer));
	// assign all fields
	buf->fvf = _fvf;
	buf->fvfsize = _fvfstructsize;
	buf->type = _type;
	buf->primitiveCount = ((_type > D3DPT_LINELIST) && (_type < D3DPT_TRIANGLEFAN) ? count-2 : count);
	if (NULL == _transform){
		D3DXMATRIX *tr = new D3DXMATRIX();
		D3DXMatrixIdentity(tr);
		_transform = tr;
	}
	buf->transform = _transform;
	buf->ptex1 = tex1;
	// set the meterial properties...
	if(_mat){
		buf->mat = *_mat;
	}else{	// or create new...
		ZeroMemory(&(buf->mat), sizeof(D3DMATERIAL9));
		buf->mat.Ambient.a = buf->mat.Diffuse.a = 1.0f;
		buf->mat.Ambient.r = buf->mat.Diffuse.r = 1.0f;
		buf->mat.Ambient.g = buf->mat.Diffuse.g = 1.0f;
		buf->mat.Ambient.b = buf->mat.Diffuse.b = 1.0f;
	}

	// allocate buffer for vertices
	if (S_OK != d3ddev->CreateVertexBuffer(count * _fvfstructsize, 0, _fvf, D3DPOOL_MANAGED, 
		&(buf->vb), NULL)) {
			delete (lpmyD3DBuffer) buf;
			return false;
	}

	// rewrite vertices and other info form 'data' to designated buffer 
	// previouslu assigned
	void *mem;
	if (S_OK != buf->vb->Lock(0, count * _fvfstructsize, (void**)&mem, 0)){
		delete (lpmyD3DBuffer) buf;
		return false;
	}else{
		memcpy(mem, data, count * _fvfstructsize);
		if (S_OK != buf->vb->Unlock()) {
			delete (lpmyD3DBuffer) buf;
			return false;
		}
	}

	// add vertices to renderer primitive list
	prims.push_back(buf);

	return true;
}

bool myD3D::addMesh(LPD3DXMESH *_mesh, DWORD _subsets, D3DXMATRIX *_transform, 
					D3DMATERIAL9 *_mats, LPDIRECT3DTEXTURE9 *_texs){
	lpmyD3DMesh m = (lpmyD3DMesh) malloc(sizeof(myD3DMesh));
	ZeroMemory(m, sizeof(myD3DMesh));
	m->subsets = _subsets;
	m->mesh = *_mesh;

	if(!_transform){
		m->transform = new D3DXMATRIX();
		D3DXMatrixIdentity(m->transform);
	}else{
		m->transform = _transform;
	}

	if (_mats){
		m->mats = _mats;
	}else{
		m->mats = new D3DMATERIAL9[_subsets];
		for (DWORD i = 0; i < _subsets; i++){
			ZeroMemory(&(m->mats[i]), sizeof(D3DMATERIAL9));
			m->mats[i].Ambient.a = m->mats[i].Diffuse.a = 1.0f;
			m->mats[i].Ambient.r = m->mats[i].Diffuse.r = 1.0f;
			m->mats[i].Ambient.g = m->mats[i].Diffuse.g = 1.0f;
			m->mats[i].Ambient.b = m->mats[i].Diffuse.b = 1.0f;
		}
	}

	if (_texs){
		m->texs = _texs;
	}else{
		m->texs = new LPDIRECT3DTEXTURE9[_subsets];
		ZeroMemory(m->texs, _subsets * sizeof(LPDIRECT3DTEXTURE9));
	}

	meshes.push_back(m);
	return true;
}

bool myD3D::addXAnimatedMeshFile(LPCWSTR path){
	CMeshHierarchy mh;
	frame = NULL;
	anim = NULL;
	HRESULT hr = D3DXLoadMeshHierarchyFromX(path, D3DXMESH_MANAGED, d3ddev, &mh, NULL, &frame, &anim);
	if (S_OK != hr){
		return false;
	}

	// if any animation in .x file
	if (anim){
		// TO DO
	}

	//if there are bones...set up skinning
	if (frame){
		maxBones = 0;
		SetupBoneMatrices(d3ddev, (D3DXFRAME_EXTENDED*)frame, NULL);
		boneMatrices = new D3DXMATRIX [maxBones];
		ZeroMemory(boneMatrices, sizeof(D3DXMATRIX)*maxBones);
		//D3DXFrameCalculateBoundingSphere(&frame, 
	}

	return true;
}

void myD3D::SetupBoneMatrices(LPDIRECT3DDEVICE9 dev, D3DXFRAME_EXTENDED *pFrame, LPD3DXMATRIX pParent){
	// Cast to our extended structure first
	D3DXMESHCONTAINER_EXTENDED* pMesh = (D3DXMESHCONTAINER_EXTENDED*)pFrame->pMeshContainer;

	// If this frame has a mesh
	if(pMesh)
	{
		// We need to remember which is the first mesh in the hierarchy for later when we 
		// update (FrameMove)
		if(!firstMesh)
			firstMesh = pMesh;
		
		// if there is skin info, then setup the bone matrices
		if(pMesh->pSkinInfo && pMesh->MeshData.pMesh)
		{
			// Create a copy of the mesh to skin into later

	// FIX: 19 September 2005 - the CloneMeshFVF method was failing with some .x exported files from some packages
	// due to the FVF not being able to be mapped. Changing to GetDeclaration solves the problem.

			D3DVERTEXELEMENT9 Declaration[MAX_FVF_DECL_SIZE];
			if (FAILED(pMesh->MeshData.pMesh->GetDeclaration(Declaration)))
				return;

			//pMesh->MeshData.pMesh->CloneMeshFVF(D3DXMESH_MANAGED, 
			//	pMesh->MeshData.pMesh->GetFVF(), device, 
			//	&pMesh->exSkinMesh);
			pMesh->MeshData.pMesh->CloneMesh(D3DXMESH_MANAGED, 
				Declaration, d3ddev, 
				&pMesh->exSkinMesh);

			// Max bones is calculated for later use (to know how big to make the bone matrices array)
			maxBones=max(maxBones,pMesh->pSkinInfo->GetNumBones());

			// For each bone work out its matrix
			for (UINT i = 0; i < pMesh->pSkinInfo->GetNumBones(); i++)
			{   
				// Find the frame containing the bone
				D3DXFRAME_EXTENDED* pTempFrame = (D3DXFRAME_EXTENDED*)D3DXFrameFind(frame, 
						pMesh->pSkinInfo->GetBoneName(i));

				// set the bone part - point is at the transformation matrix
				pMesh->exFrameCombinedMatrixPointer[i] = &pTempFrame->exCombinedTransformationMatrix;
			}

		}
	}

	// Pass on to sibblings
	if(pFrame->pFrameSibling)
		SetupBoneMatrices(d3ddev,(D3DXFRAME_EXTENDED*)pFrame->pFrameSibling, pParent);

	// Pass on to children
	if(pFrame->pFrameFirstChild)
		SetupBoneMatrices(d3ddev,(D3DXFRAME_EXTENDED*)pFrame->pFrameFirstChild, &pFrame->exCombinedTransformationMatrix);
}

bool myD3D::addXFile(LPCWSTR path, D3DXMATRIX *_globalTransform){
	LPD3DXBUFFER matBuf;
	DWORD subs = 1;
	LPD3DXMESH m;

	if (S_OK != D3DXLoadMeshFromX(path, D3DXMESH_SYSTEMMEM, d3ddev, NULL, &matBuf, NULL, &subs, &m))
		return false;

	D3DXMATERIAL *tempMat = ((D3DXMATERIAL*)matBuf->GetBufferPointer());
	D3DMATERIAL9 *materials = new D3DMATERIAL9[subs];
	LPDIRECT3DTEXTURE9 *texs = new LPDIRECT3DTEXTURE9[subs];
	for (DWORD i = 0; i < subs; i++){
		materials[i] = tempMat[i].MatD3D;
		USES_CONVERSION;	
		texs[i] = addTextureFromFile(CA2W(tempMat[i].pTextureFilename));
	}

	return addMesh(&m, subs, _globalTransform, materials, texs);
}

// loading shader file
bool myD3D::loadShaderFile(LPCWSTR path){
	LPD3DXBUFFER buff;
	if (S_OK != D3DXCreateEffectFromFile(d3ddev, path, NULL, NULL, D3DXSHADER_DEBUG, NULL, &effect, &buff)){
		return false;
	}
	return true;
}

// render the frame
void myD3D::RenderFrame(){
	// clear color buffer & z-buffer
	d3ddev->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(100, 100, 255), 1.0f, 0);
	d3ddev->Clear(0, NULL, D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
	d3ddev->BeginScene();	// start drawing the scene

	// TEMP loading Shader Technique
	D3DXHANDLE tech = effect->GetTechniqueByName("secondShader");
	D3DXHANDLE gWVP = effect->GetParameterByName(NULL, "gWVP");
	//D3DXHANDLE gTex = effect->GetParameterByName(NULL, "gTex");
	//D3DXHANDLE useTex = effect->GetParameterByName(NULL, "useTex");
	D3DXMATRIX m, n;
	d3ddev->GetTransform(D3DTS_VIEW, &m);
	d3ddev->GetTransform(D3DTS_PROJECTION, &n);

	// set camera and viewport properties
	setLookAt();
	setPerspective();

	if (!prims.empty()){
		list<lpmyD3DBuffer>::iterator i;
		for (i = prims.begin(); i != prims.end(); i++){
			d3ddev->SetFVF((**i).fvf);
			d3ddev->SetMaterial(&((**i).mat));
			//d3ddev->SetTransform(D3DTS_WORLD, (**i).transform);
			d3ddev->SetStreamSource(0, (**i).vb, 0, (**i).fvfsize);
			d3ddev->SetTexture(0, (**i).ptex1);		

			//TEMP shaders
			/*UINT pass;
			effect->SetMatrix(gWVP, &((*(**i).transform) * m * n));
			effect->SetTexture(gTex, (**i).ptex1);
			effect->SetBool(useTex, (bool)(**i).ptex1);
			effect->Begin(&pass, 0);
			effect->BeginPass(0);*/
			
			d3ddev->DrawPrimitive((**i).type, 0, (**i).primitiveCount);			

			//TEMP shaders
			/*effect->EndPass();
			effect->End();*/
		}
	}

	if (!meshes.empty()){
		list<lpmyD3DMesh>::iterator i;
		for (i = meshes.begin(); i != meshes.end(); i++){
			for (DWORD j = 0; j < (**i).subsets; j++){
				d3ddev->SetMaterial(&((**i).mats[j]));
				d3ddev->SetTransform(D3DTS_WORLD, (**i).transform);
				d3ddev->SetTexture(0, ((**i).texs[j]));

				// apply shader
				UINT pass;
				effect->SetMatrix(gWVP, &((*(**i).transform) * m * n));
				effect->Begin(&pass, 0);
				for (UINT p = 0; p < pass; p++){
					effect->BeginPass(p);
					(**i).mesh->DrawSubset(j);
					effect->EndPass();
				}
				effect->End();
			}
		}
	}

	if (frame){
		DrawFrame(frame);
		UpdateBones();
	}
	
	d3ddev->EndScene();		// end drawing the scene
	d3ddev->Present(NULL, NULL, NULL, NULL);	// show scene
}



/**
 * \brief Called to render a frame in the hierarchy
 * \param device - the Direct3D device object
 * \param frame - frame to render
 * \author Keith Ditchburn \date 18 July 2005
*/
void myD3D::DrawFrame(LPD3DXFRAME _frame) const
{
	// Draw all mesh containers in this frame
    LPD3DXMESHCONTAINER meshContainer = _frame->pMeshContainer;
    while (meshContainer)
    {
        DrawMeshContainer(meshContainer, _frame);
        meshContainer = meshContainer->pNextMeshContainer;
    }

	// Recurse for sibblings
    if (_frame->pFrameSibling != NULL)
        DrawFrame(_frame->pFrameSibling);

    // Recurse for children
	if (_frame->pFrameFirstChild != NULL)
        DrawFrame(_frame->pFrameFirstChild);
}

/**
 * \brief Called to render a mesh
 * \param device - the Direct3D device object
 * \param meshContainerBase - the mesh container
 * \param frameBase - frame containing the mesh
 * \author Keith Ditchburn \date 18 July 2005
*/
void myD3D::DrawMeshContainer(LPD3DXMESHCONTAINER meshContainerBase, LPD3DXFRAME frameBase) const
{
	//assert(device);

	// Cast to our extended frame type
	D3DXFRAME_EXTENDED *_frame = (D3DXFRAME_EXTENDED*)frameBase;		

	// Cast to our extended mesh container
	D3DXMESHCONTAINER_EXTENDED *meshContainer = (D3DXMESHCONTAINER_EXTENDED*)meshContainerBase;
	
	// Set the world transform
    d3ddev->SetTransform(D3DTS_WORLD, &_frame->exCombinedTransformationMatrix);

	// Loop through all the materials in the mesh rendering each subset
    for (UINT iMaterial = 0; iMaterial < meshContainer->NumMaterials; iMaterial++)
    {
		// use the material in our extended data rather than the one in meshContainer->pMaterials[iMaterial].MatD3D
		d3ddev->SetMaterial( &meshContainer->exMaterials[iMaterial] );
		d3ddev->SetTexture( 0, meshContainer->exTextures[iMaterial] );

		// Select the mesh to draw, if there is skin then use the skinned mesh else the normal one
		LPD3DXMESH pDrawMesh = (meshContainer->pSkinInfo) ? meshContainer->exSkinMesh: meshContainer->MeshData.pMesh;

		// Finally Call the mesh draw function
        pDrawMesh->DrawSubset(iMaterial);
    }
}

void myD3D::UpdateBones(){
	// If the model contains a skinned mesh update the vertices
	D3DXMESHCONTAINER_EXTENDED* pMesh = (D3DXMESHCONTAINER_EXTENDED*)frame->pMeshContainer; //firstMesh;
	if(pMesh && pMesh->pSkinInfo)
	{
		UINT Bones = pMesh->pSkinInfo->GetNumBones();

		// Create the bone matrices that transform each bone from bone space into character space
		// (via exFrameCombinedMatrixPointer) and also wraps the mesh around the bones using the bone offsets
		// in exBoneOffsetsArray
		for (UINT i = 0; i < Bones; ++i)
			D3DXMatrixMultiply(&boneMatrices[i],&pMesh->exBoneOffsets[i], pMesh->exFrameCombinedMatrixPointer[i]);

		// We need to modify the vertex positions based on the new bone matrices. This is achieved
		// by locking the vertex buffers and then calling UpdateSkinnedMesh. UpdateSkinnedMesh takes the
		// original vertex data (in pMesh->MeshData.pMesh), applies the matrices and writes the new vertices
		// out to skin mesh (pMesh->exSkinMesh). 

		// UpdateSkinnedMesh uses software skinning and so this is the slowest way of carrying out skinning 
		// but is easiest to describe and works on the majority of graphic devices. 
		// Other methods exist that use hardware to do this skinning - see the notes and the 
		// SDK skinned mesh sample for details

		HRESULT hr=S_OK;

		void *srcPtr;
		V(pMesh->MeshData.pMesh->LockVertexBuffer(D3DLOCK_READONLY, (void**)&srcPtr));

		void *destPtr;
		V(pMesh->exSkinMesh->LockVertexBuffer(0, (void**)&destPtr));

		// Update the skinned mesh 
		V(pMesh->pSkinInfo->UpdateSkinnedMesh(boneMatrices, NULL, srcPtr, destPtr));

		// Unlock the meshes vertex buffers
		V(pMesh->exSkinMesh->UnlockVertexBuffer());
		V(pMesh->MeshData.pMesh->UnlockVertexBuffer());
	}
};